
#include <ctype.h>
#include <string.h>
#include <kernel.h>

/*
  Flat memory module

  kernel run in a flat memory space, base address is 0x0, segment size is 4G

  0x00000000 - 0x00007BFF unused
  0x00007C00 - 0x00007DFF boot sector
  0x00007E00 - 0x00007FFF temp space for load GDTR
  0x00008000 - 0x000080FF GDT table(temp space for boot sector)
  0x00008100 - 0x00008BFF unused
  0x00008C00 - 0x00009DFF FAT space for boot sector
  0x00009E00 - 0x0000FFFF unused
  0x00010000 - 0x0001FFFF run time space for bootloader
  0x00020000 - 0x0009FFFF kernel file space with text, data, bss, rdata
  0x000A0000 - 0x000FFFFF BIOS space
  0x00100000 - 0x001007FF interrupt table
  0x00100800 - 0x0011FFFF interrupt handler
  0x00120000 - 0x001FFFFF kernel stack
  0x00200000 - 0x002FFFFF kernel heap
  0x00300000 - 0xFFFFFFFF unused

  global segment:
  index     type    base        limit       used for
  0x0008    data    0x00000000  0xFFFFFFFF  ds, es, fs, gs
  0x0010    code    0x00000000  0xFFFFFFFF  cs
  0x0018    stack   0x00000000  0xFFFFFFFF  ss
 */



#define SEG_ATTR_TYPE_W         (0x02)
#define SEG_ATTR_TYPE_S         (0x04)
#define SEG_ATTR_TYPE_X         (0x08)

#define SEG_ATTR_EXIST          (0x80)
#define SEG_ATTR_ISSEG          (0x10)

#define SEG_ATTR_SEG32          (0x40)
#define SEG_ATTR_4K             (0x80)

#define GDT_BASE                (0x00008000)
#define GDT_SIZE                (0x0100 - 1)

#define MEM_PART_COUNT_ADDR     (0x9000)
#define MEM_PART_SIZE_ADDR      (0x9004)
#define MEM_PART_INFO_ADDR      (0x9020)


int mem_part_count;

memoryframe mem_part_info[128];

memoryframe * get_memory_info()
{
    int mem_record_size;
    int i;

    read_from_real(&mem_part_count, sizeof(int), MEM_PART_COUNT_ADDR);
    read_from_real(&mem_record_size, sizeof(int), MEM_PART_SIZE_ADDR);

    if (mem_part_count > 128)
        {
        mem_part_count = 0;
        return NULL;
        }

    if (mem_record_size < sizeof(memoryframe))
        {
        mem_part_count = 0;
        return NULL;
        }

    for (i = 0; i < mem_part_count; i++)
        {
        read_from_real(&mem_part_info[i], sizeof(memoryframe), MEM_PART_INFO_ADDR + i * mem_record_size);
        }

    return mem_part_info;
}

void w_to_m(char c, int realptr)
{
    asm __volatile__
        (
        "push %%ds\n\t"
        "movw %0, %%ax\n\t"
        "movw %%ax, %%ds\n\t"
        ::"i"(SEG_GLOBAL_FLAT_SELECTOR):"%eax"
        );

    *(char *)realptr = c;

    asm __volatile__ ("pop %ds\n\t");
}

char r_from_m(int realptr)
{
    char c;
    asm __volatile__
        (
        "push %%ds\n\t"
        "movw %0, %%ax\n\t"
        "movw %%ax, %%ds\n\t"
        ::"i"(SEG_GLOBAL_FLAT_SELECTOR):"%eax");

    c = *(char *)realptr;

    asm __volatile__ ("pop %ds\n\t");

    return c;
}

void write_to_real(void * p, size_t l, int realp)
{
    int i;
    char * pt = p;
    for (i = 0; i < l; i++)
        {
        w_to_m(pt[i], realp + i);
        }
}

void read_from_real(void * p, size_t l, int realp)
{
    int i;
    char * pt = p;
    for (i = 0; i < l; i++)
        {
        pt[i] = r_from_m(realp + i);
        }
}

/* this 2 load*dt function should be modified because they are not aligned */

void loadgdt(int * base_addr, int * limit)
{
    int b[2] = {0};

    asm __volatile__
        (
        "leal %0, %%ebx\n\t"
        "sgdt %%ds:(%%ebx)\n\t"
        :"=o"(b)
        ::"%ebx"
        );

    *limit = b[0] & 0xFFFF;

    *base_addr = *(int *)(((char *)b) + 2);
}

void setgdt(int base_addr, int limit)
{
    unsigned int b[2] = {0};

    if (limit > 0x7FF)
        return;

    b[0] = (limit & 0xFFFF) << 16;
    b[1] = base_addr;

    asm __volatile__
        (
        "leal %0, %%ebx\n\t"
        "addl $2, %%ebx\n\t"
        "lgdt %%ds:(%%ebx)\n\t"
        :"=o"(b)
        ::"%ebx"
        );
}

int createSegment(int segindex, int base_addr, int limit, seg_type stype)
{
    int gdt_base;
    int gdt_limit;

    int realptr;

    SegDescriptor sd;

    loadgdt(&gdt_base, &gdt_limit);

    if (segindex > gdt_limit || segindex % sizeof(sd) != 0 || segindex == 0)
        {
        return -1;
        }

    realptr = gdt_base + segindex;

    memset(&sd, 0, sizeof(sd));

    sd.SegAtt1 = SEG_ATTR_EXIST | SEG_ATTR_ISSEG;
    sd.SegAtt2 = SEG_ATTR_SEG32;

    switch(stype)
        {
        case code_segment:
            sd.SegAtt1 |= SEG_ATTR_TYPE_X;
            break;
        case stack_segment:
            sd.SegAtt1 |= SEG_ATTR_TYPE_S;
            break;
        case data_segment:
            sd.SegAtt1 |= SEG_ATTR_TYPE_W;
            break;
        }

    if (limit > 0xFFFFF)
        {
        sd.SegAtt2 |= SEG_ATTR_4K;
        limit >>= 12;
        }

    sd.SegAtt2 |= (limit >> 16) & 0xF;
    sd.SegLength = limit & 0xFFFF;
    sd.SegBaseLow = base_addr & 0xFFFF;
    sd.SegBaseHighL = (base_addr & 0x00FF0000) >> 16;
    sd.SegBaseHighH = (base_addr & 0xFF000000) >> 24;

    write_to_real(&sd, sizeof(sd), realptr);

    return 0;
}

short int GetCurrentCodeSeg()
{
    short int cs = 0;
    asm __volatile__
        (
        "movw %%cs, %%ax\n\t"
        "movw %%ax, %0\n\t"
        :"=r"(cs)::"%eax"
        );

    return cs;
}

void init_gdt()
{
    setgdt(GDT_BASE, GDT_SIZE);
}

void init_mem()
{
    get_memory_info();

    mem_part_init();
}

